////////////////////////////////////////////////////////////////////////////
//
//  Crytek Engine Source File.
//  Copyright (C), Crytek Studios, 2010.
// -------------------------------------------------------------------------
//  File name:   3DEngineMemory.h
//  Version:     v1.00
//  Created:     07/05/2011 by Christopher Bolte
//  Compilers:   Visual Studio.NET
// -------------------------------------------------------------------------
//  History:
////////////////////////////////////////////////////////////////////////////

#ifndef SPUBackEnd_H_
#define SPUBackEnd_H_

#include <IJobManager.h>

#include <cell/gcm.h>
#include <sys/interrupt.h>
#include <sys/spu_utility.h>

#include "sn/trace.h"

#include "../JobManager.h"
#include "../PPU.h"
#include "JobStructs.h"
#include "SPULoaderDefs.h"
#include "CodePage/SPUPages.h"
#include "SPUMemManagerBase.h"

#include <../../spu/include/sn/libsn_module.h>


///////////////////////////////////////////////////////////////////////////////
// forward declarations
namespace NPageBin	{ struct SHeader; }


///////////////////////////////////////////////////////////////////////////////
// implementation of SPUBackend class
namespace JobManager {

	class CSPUBackEnd : public ISPUBackend
	{
	public:
		CSPUBackEnd();
		virtual ~CSPUBackEnd();
		
		// returns true if this jobs would be executed on SPU
		bool InvokeAsSPUJob( const char *cpJobName );

		// returns number of SPUs allowed for job scheduling
		virtual const unsigned int GetSPUsAllowed() const;		
		
		// returns spu driver size, all data must be placed behind it
		virtual const unsigned int GetDriverSize() const;			

		// initializes all allowed SPUs
		virtual const bool InitSPUs(const char* sSPURepositoryName, const int nSPUThreadCount );

		// enables spu debugging for a particular job
		virtual void EnableSPUJobDebugging( const void*cJobHandle );

		//registers a variable to check if the profiling data should be transferred back
		virtual void RegisterProfileStatVar(int *pVar);

		// enables/disables spu profiling
		virtual void EnableSPUProfiling(const uint8 cEnable=0);
		
		// print performance stats
		virtual void PrintPerfStats(const volatile NSPU::NDriver::SJobPerfStats* pPerfStats, const char* cpJobName) const;

		// obtains and resets the SPU stats of the last frame
		virtual void GetAndResetSPUFrameStats(JobManager::SSPUFrameStats& rStats, const bool=true);
		virtual void GetAndResetSPUFrameStats(JobManager::SSPUFrameStats& rStats, const JobManager::SFrameProfileData*& rpCurFrameProfVec, uint32& rCount);
		
		// obtains and resets the SPU function profiling stats of the last frame
		virtual void GetAndResetSPUFuncProfStats(const JobManager::SFrameProfileData*& rpCurFuncProfStatVec, uint32& rCount, const uint32 cThresholdUSecs=100);

		// initialize values required for spu-libgcm usage
		virtual void GcmInit( const uint32 cCountDeviceThreadEA, void *const __restrict pReportLoc, const uint32 cRSXBase, CellGcmContextData *const __restrict pCellGcmCurrentContext, const uint32 cCurGCMCmdOffset, const uint32 cGCMCmdResetOffset, const uint32 cInjectBufOff );
		// shuts down the SPU backend
		virtual bool ShutDown();

		// retrieves the name of a job
		const char* GetJobName(const uint32 cId);

		// add a job to the queue of the SPUBackend
		virtual JobManager::EAddJobRes AddJob( JobManager::CJobDelegator& RESTRICT_REFERENCE crJob, const uint32 cOpMode, const JobManager::TJobHandle cJobHandle, JobManager::SInfoBlock &rInfoBlock );

		// sets the job filter, to exclude jobs from running with this backend
		virtual void SetJobFilter( const char * pFilter);
		
		// updates internal state and draws the job system profiler
		virtual void Update();

		// no-op for this backend (all is handled in InitSPU)
		virtual bool Init(uint32 nSysMaxWorker);

		// return the number of available worker threads for client code
		virtual uint32 GetNumWorkerThreads() const;

		// returns true if a valid SPU-Repository was loaded
		virtual bool HasValidSPURepository() const;

		// suspends a shared worker thread, to allow other SPU threads to run
		virtual void SuspendSharedWorkerThread();

		// resume execution of a previously suspended worker thread
		virtual void ResumeSharedWorkerThread();

		// returns the job string table
		const std::vector<JobManager::SJobStringHandle>& GetJobStringTable() const;		

		//cleans up allocated/freed memory from SPU, cIgnoreProcessing true ignores the running state of SPU
		//  this allows the cleanup to be called from SPU itself (which is then in the running state)
		void UpdateSPUMemManSingleSPU(const uint32 cSPUIndex);	
		
		// returns the memory bucket meta information for this SPU
		NSPU::SMemoryBucket& GetMemoryBucket( uint32 cSPUIndex );

	private:
		// Create SPU Profiling Data members
		void CreateSPUProfilingData();					
		
		// creates the page directory for SPU, each page index gets the PPU EA
		void CreateSPUPageDir();

		// creates the job handle <-> string table
		void CreatePageJobStringTable();

		// loads the SPU loader and the SPU driver into SPU local store
		//	cRealSPUId			:  ID for the SPU
		void LoadSPULoaderDriver (const unsigned int cRealSPUId, const unsigned int cSPUIndex	) const;

		// loads a SPU-Repository from a filename, and sets it up
		bool LoadAndCreateSPURepository( const char *sSPURepositoryName );

		// initializes a sys_spu_thread_argument_t for SPU Threads
		void SetupSPUDriverArguments( sys_spu_thread_argument_t &args, bool bIsSharedWithOS, uint32 nSPUIndex, uint32 nWorkerIndex );

		// initializes the bucket headers for each SPU
		void InitBucketMemory();

		// Entry function for thread to handle SPU events, one for each SPU Thread
		static void SPUThreadEventHandler( uint64_t nSPUThreadId);

		// retrieves spu memory control
		ILINE JobManager::CSPUMemAreaMan& GetMemAreaMan();
					
		// returns the index to use for the frame profiler
		uint32 GetCurrentFrameBufferIndex() const; 				

		// returns access to the internal jobqueue object
		ILINE SJobQueue_SPUBackend& GetJobQueue();







#if defined(JOBMANAGER_SUPPORT_TUNER_CAPTURES)
		// functions to support libtrace to capture SPUs in tuner
		static int openCallback(CellTraceAttr *attr, CellTraceOpenParams *openParam, sys_addr_t traceBuffer, sys_addr_t paramBuffer);
		static int closeCallback(CellTraceAttr *attr);
		static int startCallback(CellTraceAttr *attr);
		static int stopCallback(CellTraceAttr *attr);
#endif

		// type of the Job String handle collection
		typedef std::vector<NPageBin::SJobStringHandle*> TJobStringVec;


		// this symbol also acts as the relative base of global/static variables, mangled name _ZN4JobManager10CJobManager17scInitalSPULoaderE
		static uint32												scInitalSPULoader[NSPU::scLoaderTextSizeBytes >> 2] _ALIGN(128);		// initial SPU loader
		static NSPU::SLoaderParams					scSPULoaderParam;																										// loader parameters, cannot be on the stack because its getting DMAd from

		JobManager::SJobQueue_SPUBackend		m_JobQueue _ALIGN(128);																							// SPU job queue node where jobs are pushed into and from		
		JobManager::CSPUMemAreaMan					m_MemAreaMan _ALIGN(128);																						// memory allocation/deallocation request manager for SPU				

		SSPUEventAcknowledgeLine						m_SPUAcknowledgeLine[JobManager::SPUBackend::scMaxSPU];							// one cacheline for each SPU, to send back results from callbacks	(NOTE: Needs to be before m_PageInfo to get it's addess on SPU)

		NSPU::SPageDirInfo									m_PageInfo _ALIGN(16);																							// page info for SPUDriver's		
		
		NSPU::SMemoryBucket*								m_pBucketDirMem[JobManager::SPUBackend::scMaxSPU];									// allocated bucket memory for each SPU
		NSPU::SPageInfo*										m_pPageDir;																													// page dir for SPU, each page gets the EA		
		const char*													m_pSPUFilter;																												// filter to exclude jobs from SPUBackend
		uint32															m_JobNum;																														// total number of distinct SPU jobs loaded from repository

		NPageBin::SHeader *									m_pSPURep;																													// pointer to SPU repository, memory is 128 byte aligned
		uint32															m_NumSPUAllowed;																										// number of SPUs allowed to be used for job scheduling, 1..scMaxSPU
		uint32															m_DriverSize;																												// driver size, SPU jobs must place data after it
		void*																m_SPUJobDebugHandle;																								// current job handle for debugging is to be enabled, 0 if non default		
		uint32															m_GcmAddressBase;																										// base address for cellGcmAddressToOffset, passed to SPUs
		uint32															m_CellGcmCurrentContext;																						// address of global GCM-context
		uint32*															m_pFuncProfilingArea;																								// timing destination n for each job from SPU, cannot overlap in any cache line, 128 byte aligned, dyn.allocated				
		bool																m_Initialized;																											// true if SPUs have been initialized		

		TJobStringVec												m_JobStringOffsets;																									// pointer to job strings for each job id												
		std::vector<JobManager::SJobStringHandle> m_JobStringTable;																							// sorted job string table to retrieve a handle from a string
		std::map<uint32,uint32>							m_JobNameMap;																												// to name job handles to their respective job name								
		bool																m_bValidSPURepositoryFound;																					// keep track if we found a valid SPU repository

		sys_spu_thread_group_t							m_groupDXPS;																												// SPU thread group id for DXPS group
		sys_spu_thread_group_t							m_groupDedicated;																										// SPU thread group id for dedicated worker threads
		sys_spu_thread_group_t							m_groupShared;																											// SPU thread group id for shared worker threads

		sys_spu_thread_t										m_SPUThreadId[JobManager::SPUBackend::scMaxSPU];										// SPU thread ids
		sys_event_queue_t										m_event_queueToPPU[JobManager::SPUBackend::scMaxSPU];								// event queue for communication from SPU -> PPU																									

		SSPUDriverParameter									m_SPUDriverParameter[JobManager::SPUBackend::scMaxSPU];							// storage for parameters passed by their address to the SPU Driver

		// members required for tuner capturing support
#if defined(JOBMANAGER_SUPPORT_TUNER_CAPTURES)
		CellTraceMode												m_nSPUTraceMode;																										// mode set by tuner for capturing
		CellTraceSpuManagerControl *				m_pSPUTraceControl;																									// control structure in trace buffer to tuner captures
		uint32															m_nSPUTraceBufferSize;																							// size of the trace buffer
		uint32															m_nSPUTraceAreaSize;																								// trace area size
		uint32															m_nSPUTraceRunning;																									// true if a tuner trace is running
		uint32															m_nSPUTraceRegisteredId;																						// id used for registering the trace
#endif

		// members required for per function profiling







		
		// members required for profiling jobs in the frame profiler





				
	};

} // namespace JobManager


///////////////////////////////////////////////////////////////////////////////
// implementation of SPUBackend inline functions
inline const unsigned int JobManager::CSPUBackEnd::GetSPUsAllowed() const
{
	return m_NumSPUAllowed;
}

///////////////////////////////////////////////////////////////////////////////
inline const unsigned int JobManager::CSPUBackEnd::GetDriverSize() const	
{
	return m_DriverSize;
}

///////////////////////////////////////////////////////////////////////////////
inline void JobManager::CSPUBackEnd::RegisterProfileStatVar(int *pVar)
{



}

///////////////////////////////////////////////////////////////////////////////
inline void JobManager::CSPUBackEnd::EnableSPUProfiling(const uint8 cEnable)
{
	static uint32 nEnable = 0;
	if( nEnable != cEnable )
	{
		nEnable = cEnable;
		for( int i = 0 ; i < JobManager::SPUBackend::scMaxSPU ; ++i )
			sys_spu_thread_write_ls(m_SPUThreadId[i],JOBMANAGER_LS_FRAMEPROFILER_ENABLED,nEnable,4);
	}
}

///////////////////////////////////////////////////////////////////////////////
inline void JobManager::CSPUBackEnd::GcmInit(const uint32 cCountDeviceThreadEA, void *const __restrict pReportLoc, const uint32 cRSXBase, CellGcmContextData *const __restrict pCellGcmCurrentContext, const uint32 cCurGCMCmdOffset, const uint32 cGCMCmdResetOffset, const uint32 cInjectBufOff )
{
	//get offset of main command buffer
	m_GcmAddressBase											= cCurGCMCmdOffset;
	m_PageInfo.gcmCmdAddressBase					= cCurGCMCmdOffset;
	m_CellGcmCurrentContext								= (uint32)pCellGcmCurrentContext;
	m_PageInfo.gcmGlobalPPUContext				= (uint32)pCellGcmCurrentContext;
	CellGcmControl *const pGCMControlReg	= cellGcmGetControlRegister();
	m_PageInfo.gcmGlobalPPUControlReg			= offsetof(CellGcmControl, put) + (uint32)(void*)pGCMControlReg;
	m_PageInfo.gcmRsxBaseAddress					= cRSXBase;
	m_PageInfo.reportEA										= (unsigned int)pReportLoc;
	m_PageInfo.gcmCountDeviceThreadEA			=	cCountDeviceThreadEA;
	m_PageInfo.profilingEnabled						= 0;
	m_PageInfo.funcProfTimingEA						= (uint32)m_pFuncProfilingArea;
	m_PageInfo.gcmInjectBufOff						= cInjectBufOff;
	m_PageInfo.gcmCmdResetOffset					= cGCMCmdResetOffset;
}

///////////////////////////////////////////////////////////////////////////////
inline JobManager::CSPUMemAreaMan& JobManager::CSPUBackEnd::GetMemAreaMan()
{
	return m_MemAreaMan;
}

///////////////////////////////////////////////////////////////////////////////
inline const std::vector<JobManager::SJobStringHandle>& JobManager::CSPUBackEnd::GetJobStringTable() const
{
	return m_JobStringTable;
}

///////////////////////////////////////////////////////////////////////////////
inline uint32 JobManager::CSPUBackEnd::GetCurrentFrameBufferIndex() const 
{



	return 0;

}

///////////////////////////////////////////////////////////////////////////////
inline JobManager::SJobQueue_SPUBackend& JobManager::CSPUBackEnd::GetJobQueue()
{
	return m_JobQueue;
}

///////////////////////////////////////////////////////////////////////////////
inline void JobManager::CSPUBackEnd::SetJobFilter( const char * pFilter)
{
	m_pSPUFilter = pFilter;
}

///////////////////////////////////////////////////////////////////////////////
inline bool JobManager::CSPUBackEnd::Init(uint32 nSysMaxWorker)
{
	return true;
}

///////////////////////////////////////////////////////////////////////////////
inline uint32 JobManager::CSPUBackEnd::GetNumWorkerThreads() const
{ 	
	/* one SPU is resevered for DXPS */
	return (m_NumSPUAllowed ? m_NumSPUAllowed - 1 : 0) + 1;
}

///////////////////////////////////////////////////////////////////////////////
inline bool JobManager::CSPUBackEnd::HasValidSPURepository() const 
{ 
	return m_bValidSPURepositoryFound; 
}

///////////////////////////////////////////////////////////////////////////////
inline NSPU::SMemoryBucket& JobManager::CSPUBackEnd::GetMemoryBucket(uint32 cSPUIndex)
{ 
	assert(cSPUIndex < JobManager::SPUBackend::scMaxSPU);
	assert(m_pBucketDirMem[cSPUIndex] != NULL);
	return *m_pBucketDirMem[cSPUIndex];
}
 
///////////////////////////////////////////////////////////////////////////////







#endif // SPUBackEnd_H_
